package cn.sdormitory.smartclass.service.impl;

import cn.hutool.core.lang.Assert;
import cn.sdormitory.attendance.dao.AttendanceRulesDao;
import cn.sdormitory.attendance.entity.AttendanceRules;
import cn.sdormitory.basedata.dao.BClassDao;
import cn.sdormitory.basedata.dao.BDeviceDao;
import cn.sdormitory.basedata.dao.BStudentDao;
import cn.sdormitory.basedata.dao.ClassDeviceDao;
import cn.sdormitory.basedata.dto.DeviceWithClass;
import cn.sdormitory.basedata.entity.BClass;
import cn.sdormitory.basedata.entity.BStudent;
import cn.sdormitory.common.utils.RedisUtils;
import cn.sdormitory.common.utils.StringUtils;
import cn.sdormitory.smartclass.dao.ClassAttenceDao;
import cn.sdormitory.smartclass.entity.AdClassAttence;
import cn.sdormitory.smartclass.entity.ClassAttence;
import cn.sdormitory.smartclass.form.StudentClassAttendanceQueryForm;
import cn.sdormitory.smartclass.service.ClassAttenceService;
import cn.sdormitory.smartclass.vo.ClassAttenceVo;
import cn.sdormitory.smartclass.vo.StudentClassAttendanceVo;
import cn.sdormitory.sys.dao.SysConfigDao;
import cn.sdormitory.sys.entity.SysConfig;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @创建人：zhouyang
 * @创建时间：2021/12/20 16:29
 * @version：V1.0
 */
@Slf4j
@Service("classAttenceServiceImpl")
public class ClassAttenceServiceImpl extends ServiceImpl<ClassAttenceDao, ClassAttence> implements ClassAttenceService {

    @Autowired
    private BStudentDao bStudentDao;

    @Autowired
    private BClassDao bClassDao;

    @Autowired
    private ClassDeviceDao classDeviceDao;

    @Autowired
    private BDeviceDao bDeviceDao;

    @Autowired
    private AttendanceRulesDao attendanceRulesDao;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private SysConfigDao sysConfigDao;

    @Autowired
    ClassAttenceDao classAttenceDao;

    @Override
    public int getAppClaStuAttCount(Long classId, String accessDate, String timeSlot) {
        return this.baseMapper.getAppClaStuAttCount(classId,accessDate,timeSlot);
    }

    @Override
    public List<ClassAttenceVo> getAppClaStuAttByClaIdAndSlot(Long classId, String accessDate, String timeSlot) {
        return this.baseMapper.getAppClaStuAttByClaIdAndSlot(classId,accessDate,timeSlot);
    }

    @Override
    public List<ClassAttenceVo> getAppClaStuByClaId(Long classId) {
        return this.baseMapper.getAppClaStuByClaId(classId);
    }

    @Override
    public int updateClaAttStatus(String studentNo, String accessDate, String attenceStatus, String timeSlot) {
        return this.baseMapper.updateClaAttStatus(studentNo,accessDate,attenceStatus,timeSlot);
    }

    @Override
    public int addClassAttence(AdClassAttence adClassAttence) {
        int count = 0;
        try {
            String[] studentNoArray = adClassAttence.getStudentNoArray();
            Integer[] statusArray = adClassAttence.getStatusArray();
            if(studentNoArray!=null && studentNoArray.length>0) {
                for (int i = 0; i < studentNoArray.length; i++) {
                    ClassAttence sdAttence = new ClassAttence();

                    sdAttence.setStudentNo(studentNoArray[i]);
                    sdAttence.setAccessDate(adClassAttence.getAttenceDate());
                    sdAttence.setAttenceStatus(statusArray[i] + "");
                    sdAttence.setClassId(adClassAttence.getClassId());
                    sdAttence.setTimeSlot(adClassAttence.getTimeSlot());
                    sdAttence.setIsDuty(adClassAttence.getIsDuty());
                    int attenceCount = this.baseMapper.getClaAttByDateAndStuNo(sdAttence.getStudentNo(),sdAttence.getAccessDate(),sdAttence.getTimeSlot());
                    // 根据学号以及考勤时间判断考勤表中是否有存在的记录
                    if(attenceCount<1) {
                        this.baseMapper.insert(sdAttence);
                    }else{
                        // 根据学号更新考勤状态
                        //System.out.println("------- 开始执行更新考勤信息： 学号： "+sdAttence.getStudentNo()+"考勤时间： "+format.format(sdAttence.getAccessDate()) +"考勤状态： "+sdAttence.getAttenceStatus()+时间段);
                        this.baseMapper.updateClaAttStatus(sdAttence.getStudentNo(),sdAttence.getAccessDate(),sdAttence.getAttenceStatus(),sdAttence.getTimeSlot());
                    }
                }
            }
            count = 1;
        }catch (Exception ex){
            ex.printStackTrace();
            count = 0;
        }

        return  count;
    }

    @Override
    public List<ClassAttenceVo> getAppClaStayStuByClaId(Long classId) {
        return this.baseMapper.getAppClaStayStuByClaId(classId);
    }

    @Override
    @Async
    public boolean receiveClsAttendanceRecords(List<Map<String, String>> records) throws Exception {
        log.info("......start......");
        long begin = System.currentTimeMillis();
        //提交数据列表
        List<ClassAttence> classAttenceList = new ArrayList<>();
        //所有学生数据
        List<BStudent> allStudent = bStudentDao.getAllStudent();
        //所有班级信息
        List<BClass> allBClass = bClassDao.getAllClass();
        //所有考勤机信息
        List<DeviceWithClass> allDeviceWithClassId = bDeviceDao.getAllDeviceWithClassId();
        //所有考勤规则
//        List<AttendanceRules> allAttendanceRules = attendanceRulesDao.getAllAttendanceRules();
        //考勤迟到范围
        LambdaQueryWrapper<SysConfig> sysConfigWrapper = new LambdaQueryWrapper<>();
        sysConfigWrapper.eq(SysConfig::getParamKey,"attendance.late.range");
        SysConfig sysConfig = sysConfigDao.selectOne(sysConfigWrapper);

        for(Map<String,String> map : records){
            log.info("=====> 开始同步班级考勤数据...");
            String date = map.get("stuTime");
            String stuId = map.get("userId");
            String serialNo = map.get("deviceCode");

            if(StringUtils.isEmpty(date)){
                log.info("日期为空");
                continue;
            }
            if(StringUtils.isEmpty(stuId)){
                log.info("学生ID为空");
                continue;
            }
            if(StringUtils.isEmpty(serialNo)){
                log.info("序列号为空");
                continue;
            }
            log.info("当前学生信息：" + stuId + "，考勤时间：" + date + "，打卡设备：" + serialNo);

            //匹配是否存在此学生，根据手机号码匹配
            BStudent stu = null;
            for (int i = 0; i < allStudent.size(); i++) {
                if(Long.valueOf(stuId).equals(allStudent.get(i).getId())){
                    stu = allStudent.get(i);
                    break;
                }
            }
            if(stu == null){
                log.info("找不到此学生");
                log.info("=====> 结束...");
                continue;
            }

            //当前学生已经存在，则判断当前学生是不是多次打卡，多次打卡则只取其中一次记录
            boolean isRepeat = false;
            if(classAttenceList.size() > 0 || classAttenceList != null){
                for (int i = 0; i < classAttenceList.size(); i++) {
                    if(stu.getStudentNo().equals(classAttenceList.get(i).getStudentNo())){
                        isRepeat = true;
                        break;
                    }
                }
            }
            if(isRepeat){
                log.info("该学生重复打卡");
                log.info("=====> 结束...");
                continue;
            }

            //判断当前学生是否在对应考勤机器打卡,循环班级考勤机数据，匹配是否存在绑定关系
            boolean isMatch = false;
            for (int i = 0; i < allDeviceWithClassId.size(); i++) {
                //获取当前班级绑定的设备信息
                if(stu.getClassId().equals(allDeviceWithClassId.get(i).getClassId())){
                    //判断当前班级绑定的设备的序列号是否与当前数据序列号匹配,找到匹配数据则结束当前循环
                    if(serialNo.equals(allDeviceWithClassId.get(i).getSerialNo())){
                        isMatch = true;
                        break;
                    }
                }
            }
            if(!isMatch){
                log.info("该学生未在指定考勤机打卡");
                log.info("=====> 结束...");
                continue;
            }

            //查找当前学生班级
            Long classId = stu.getClassId();
            BClass bClass = null;
            for (int i = 0; i < allBClass.size(); i++) {
                if(classId.equals(allBClass.get(i).getId())){
                    bClass = allBClass.get(i);
                    break;
                }
            }
            if(bClass == null){
                log.info("找不到该学生班级");
                log.info("=====> 结束...");
                continue;
            }

            //当前打卡学生匹配对应打卡机，进行匹配考勤时间是否在当前所在班级规定的考勤时间范围
            ClassAttence classAttence = new ClassAttence();
            String accessDate = date.split(" ")[0];
            classAttence.setAccessDate(accessDate);

            if(bClass.getClassAttendanceRules() == null) {
                log.info("找不到该学生班级的考勤规则");
                log.info("=====> 结束...");
                continue;
            }

            String[] rulesIds = bClass.getClassAttendanceRules().split(",");
            //循环当前班级考勤规则
            for(int i = 0; i < rulesIds.length; i++){
                AttendanceRules rules = attendanceRulesDao.selectById(Long.valueOf(rulesIds[i]));
                String status = judgeAttendanceStatus(date, rules.getAttendanceStartTime(), rules.getAttendanceEndTime(), sysConfig.getParamValue());
                if("-1".equals(status)){
                    continue;
                }else {
                    classAttence.setTimeSlot(rules.getTimeSlot());
                    classAttence.setClassId(classId);
                    classAttence.setStudentNo(stu.getStudentNo());
                    classAttence.setAttenceStatus(status);
                    break;
                }

            }

            if(classAttence.getStudentNo() == null){
                log.info("该学生未在指定时间打卡");
                log.info("=====> 结束...");
                continue;
            }

            int attenceCount = this.baseMapper.getClaAttByDateAndStuNo(classAttence.getStudentNo(),classAttence.getAccessDate(),classAttence.getTimeSlot());
            // 根据学号以及考勤时间判断考勤表中是否有存在的记录
            if(attenceCount < 1) {
                classAttenceList.add(classAttence);
                log.info("=====> 正常打卡，结束...");
            }else {
                log.info("=====> 数据库中存在当前学生当日已打卡记录，结束...");
            }
        }
        log.info("数据总量：" + records.size());
        if(classAttenceList.size() <= 0){
            log.info("无可匹配数据");
            log.info("......end......");
            return true;
        }else {
            boolean b = this.saveBatch(classAttenceList);
            long end = System.currentTimeMillis();
            log.info("成功数据：" + classAttenceList.size());
            log.info("执行时长：" + (end - begin) + "毫秒。");
            log.info("......end......");
            return b;
        }
    }

    @Override
    public Map<String, List<String>> getClassAttenceStuByCondition(ClassAttence condition) {
        //正常打卡学生
        condition.setAttenceStatus("1");
        List<String> normalStudent = classAttenceDao.getClassAttenceStuByCondition(condition);
        //迟到打卡学生
        condition.setAttenceStatus("2");
        List<String> lateStudent = classAttenceDao.getClassAttenceStuByCondition(condition);
        //请假打卡学生
        condition.setAttenceStatus("3");
        List<String> leaveStudent = classAttenceDao.getClassAttenceStuByCondition(condition);
        //未打卡学生
        List<String> unClockInStudent = classAttenceDao.getUnClockInStudent(condition);

        Map<String, List<String>> result = new HashMap<>();
        result.put("正常",normalStudent);
        result.put("迟到",lateStudent);
        result.put("请假",leaveStudent);
        result.put("未打卡",unClockInStudent);
        return result;
    }

    @Override
    public IPage<StudentClassAttendanceVo> getStudentClassAttendancePage(StudentClassAttendanceQueryForm form) {
        Assert.notNull(form.getStudentId(),"学生编号不能为空");
        BStudent studentUser = bStudentDao.selectOne(Wrappers.<BStudent>lambdaQuery()
                .select(BStudent::getStudentNo, BStudent::getStudentName, BStudent::getStuPhoto)
                .eq(BStudent::getId, form.getStudentId())
        );
        Assert.notNull(studentUser,"根据学号获取不到学生信息");
        Assert.notBlank(studentUser.getStudentNo(),"学生学号不能为空");
        form.setStudentNo(studentUser.getStudentNo());
        @SuppressWarnings("unchecked")
        LambdaQueryWrapper<ClassAttence> wrapper = form.query().lambda()
                .select(ClassAttence::getAccessDate, ClassAttence::getAttenceStatus, ClassAttence::getTimeSlot)
                .orderByDesc(ClassAttence::getAccessDate, ClassAttence::getTimeSlot);
        Page<ClassAttence> classAttencePage = classAttenceDao.selectPage(form.generatePage(), wrapper);
        return form.convertPage(classAttencePage,item-> new StudentClassAttendanceVo()
                .setStudentNo(studentUser.getStudentNo())
                .setStudentName(studentUser.getStudentName())
                .setStudentPhoto(studentUser.getStuPhoto())
                .setDate(item.getAccessDate())
                .setStatus(item.getAttenceStatus())
                .setTimeSlot(item.getTimeSlot()));
    }

    /**
     *
     * @param targetTime 目标时间(yyyy-MM-dd HH:mm:ss)
     * @param start 起始时间(HH:mm)
     * @param end 结束时间(HH:mm)
     * @param range 结束时间后续范围(mm)
     * @return
     * @throws Exception
     */
    private String judgeAttendanceStatus(String targetTime,String start,String end,String range){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        StringBuilder startTime = new StringBuilder(targetTime.split(" ")[0]);
        startTime.append(" ").append(start).append(":00");
        StringBuilder endTime = new StringBuilder(targetTime.split(" ")[0]);
        endTime.append(" ").append(end).append(":00");
        long targetMilliseconds = 0;
        long startMilliseconds = 0;
        long endMilliseconds = 0;
        long rangeMilliseconds = Integer.valueOf(range) * 60 * 1000;
        try {
            targetMilliseconds = sdf.parse(targetTime).getTime();
            startMilliseconds = sdf.parse(startTime.toString()).getTime();
            endMilliseconds = sdf.parse(endTime.toString()).getTime();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        if(targetMilliseconds >= startMilliseconds && targetMilliseconds <= endMilliseconds){
            return "1";
        }else if(targetMilliseconds >= endMilliseconds && targetMilliseconds <= (endMilliseconds + rangeMilliseconds)){
            return "2";
        }else {
            return "-1";
        }
    }

}
